// Copyright 2013 Stephane SOPPERA
//
// This file is part of 3ds-image-uploader.
//
// 3ds-image-uploader is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// 3ds-image-uploader is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with 3ds-image-uploader.  If not, see <http://www.gnu.org/licenses/>.

function tokens(s) {
    ret = [];
    var tokenStart = -1;
    var tokenStr = false;
    for (var i = 0; i < s.length; ++i) {
	var c = s.charAt(i);
	var cc = s.charCodeAt(i);
	if (cc == 0x22) {
	    if (tokenStr) {
		ret.push(s.slice(tokenStart + 1, i))
		tokenStart = -1;
		tokenStr = false;
	    } else {
		if (tokenStart >= 0)
		    ret.push(s.slice(tokenStart, i));
		tokenStart = i;
		tokenStr = true;
	    }
	} else if (cc == 0x20) {
	    if (! tokenStr) {
		if (tokenStart >= 0) {
		    ret.push(s.slice(tokenStart, i));
		    tokenStart = -1;
		}
	    }
	} else if (cc >= 40 && cc <= 41 || cc == 44 || cc >= 58 && cc <= 64 || cc >= 91 && cc <= 93) {
	    if (! tokenStr) {
		if (tokenStart >= 0) {
		    ret.push(s.slice(tokenStart, i));
		    ret.push(c);
		} else
		    ret.push(s.slice(i, i+1));
		tokenStart = -1;		
	    }
	} else if (tokenStart < 0)
	    tokenStart = i;
    }

    if (tokenStart >= 0)
	ret.push(s.slice(tokenStart));

    return ret;
}

function ContentType(type) {
    this.type = type;
    this.parameters = {};
}

ContentType.prototype.toString = function () {
    var ret = '{ type: ';
    if (this.type === undefined)
	ret += "undefined";
    else
	ret += '"' + this.type + '"';
    ret += ", parameters: {";
    var first = true;
    for (var p in this.parameters) {
	if (! this.parameters.hasOwnProperty(p))
	    continue;
	if (first) {
	    ret += " ";
	    first = false;
	} else
	    ret += ", ";
	ret += p;
	ret += ": ";
	ret += '"' + this.parameters[p] + '"';
    }
    if (! first)
	ret += ' ';
    ret += '} }';
    return ret;
}

function parseContentTypeTks(contentTypeTks) {
    var ret = new ContentType();

    // the type is the first token
    ret.type = contentTypeTks[0];

    // parse parameters
    var i = 1;
    while (i < contentTypeTks.length) {
	// stop on first encoding error
	if (contentTypeTks[i] != ';')
	    break;

	// go to next token
	++i;
	if (i >= contentTypeTks.length)
	    break;

	// get attribute
	var attr = contentTypeTks[i];

	// go to next token
	++i;
	if (i >= contentTypeTks.length)
	    break;

	// break is not expected token
	if (contentTypeTks[i] != '=')
	    break;

	// go to next token
	++i;
	if (i >= contentTypeTks.length)
	    break;

	// get value
	var val = contentTypeTks[i];

	ret.parameters[attr] = val;

	// go to next token
	++i;
    }

    return ret;
}

function parseContentType(contentType) {
    var tks = tokens(contentType);

    return parseContentTypeTks(tks);
}

var MimeParser = (function () {
    function findDelimiter(buffer, delimiter, start) {
	for (var i = start; i <= buffer.length - delimiter.length; ++i) {
	    var di = 0;
	    for (; di < delimiter.length; ++di) {
		if (buffer[i+di] != delimiter.charCodeAt(di)) {
		    break;
		}
	    }
	    if (di == delimiter.length)
		return i;
	}
	return -1;
    }

    function bypassPreamble() {
	var start = 0;
	var delimiterIdx = -1;
	while (1) {
	    var delimiterIdx = findDelimiter(this.buffer, this.dashBoundary, start);
	    if (delimiterIdx == -1)
		return;
	    if (delimiterIdx == 0)
		break;

	    if (delimiterIdx == 1)
		start = 2
	    else if (this.buffer[delimiterIdx - 1] == 0xA &&
		     this.buffer[delimiterIdx - 2] == 0xD)
		break;
	    else
		start = delimiterIdx + 1;
	}

	this.buffer = this.buffer.slice(delimiterIdx);
	return parseBoundaryLine;
    }

    function parseBoundaryLine() {
	var eol = findDelimiter(this.buffer, "\r\n", 0);
	if (eol == -1)
	    return;
	
	if (eol >= this.dashBoundary.length + 2 &&
	    this.buffer[this.dashBoundary.length] == 45 &&
	    this.buffer[this.dashBoundary.length + 1] == 45) {
	    return parseEnd;
	}

	this.buffer = this.buffer.slice(eol + 2);
	return parseHeaders;
    }

    function parseHeaders() {
	var eol = findDelimiter(this.buffer, "\r\n", 0);
	if (eol == -1)
	    return;
	else if (eol == 0) {
	    // we reached last line of header
	    this.buffer = this.buffer.slice(eol + 2);
	    return parseBytes;
	} else {
	    // line to decode
	    var tks = tokens(this.buffer.slice(0, eol).toString());
	    
	    if (tks.length >= 3) {
		this.partHeaders[tks[0].toLowerCase()] = parseContentTypeTks(tks.slice(2));
	    }
	    
	    // continue iteration
	    this.buffer = this.buffer.slice(eol + 2);
	    return parseHeaders;
	}
    }

    function parseBytes() {
	var limit = findDelimiter(this.buffer, "\r\n" + this.dashBoundary, 0);
	if (limit == -1)
	    return;
	
	this.onPart(this.partHeaders, this.buffer.slice(0, limit));

	this.partHeaders = {};

	this.buffer = this.buffer.slice(limit + 2);
	return parseBoundaryLine;
    }

    function parseEnd() {
	this.buffer = new Buffer(0);
    }

    function MimeParser(boundary, onPart) {
	this.buffer = new Buffer(0);
	this.dashBoundary = '--' + boundary;
	this.updateFunction = bypassPreamble;
	this.partHeaders = {};
	this.onPart = onPart;
    };

    MimeParser.prototype.update = function (chunck) {
	this.buffer = Buffer.concat([this.buffer, chunck]);
	while (true) {
	    var func = this.updateFunction();
	    if (func === undefined)
		break;
	    this.updateFunction = func;
	}
    };

    return MimeParser;
})();


exports.parseContentType = parseContentType;
exports.MimeParser = MimeParser;
exports.ContentType = ContentType;
